﻿using System;
using System.Diagnostics;
using System.Linq;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Frames;

namespace RayDen.RayEngine.Data
{
    public class SunLight : SkyLight
    {
        public override string Name
        {
            get { return "Sun"; }
        }

        public float RelativeSize
        {
            get { return this.relSize; }
            set { this.relSize = value; }
        }

        public SunLight(float turb, Vector sd):base(turb, sd.Normalize())
        {
            this.turbidity = turb;
            this.sundir = sd.Normalize();
            this.InitSun();
        }

        public SunLight(FrameLightsource s)
            :this( s.Parameters.Get(SkyLightTurbulence, 2.5f),s.Parameters.Get(SkyLightSunDir, new Vector(0f,0f,1f)))
        {}

        private void InitSun()
        {
            base.Init();
            Vector.CoordinateSystem(sundir, out x, out y);

            // Values from NASA Solar System Exploration page
            // http://solarsystem.nasa.gov/planets/profile.cfm?Object=Sun&Display=Facts&System=Metric
            const float sunRadius = 695500f;
            const float sunMeanDistance = 149600000f;

            if (relSize * sunRadius <= sunMeanDistance)
            {
                sin2ThetaMax = relSize * sunRadius / sunMeanDistance;
                sin2ThetaMax *= sin2ThetaMax;
                cosThetaMax = MathLab.Sqrt(1f - sin2ThetaMax);
            }
            else
            {
                cosThetaMax = 0f;
                sin2ThetaMax = 1f;
            }

            Vector wh = Vector.Normalize(sundir);
            phiS = Vector.SphericalPhi(ref wh);
            thetaS = Vector.SphericalTheta(ref wh);
            // NOTE - lordcrc - sun_k_oWavelengths contains 64 elements, while sun_k_oAmplitudes contains 65?!?
            IrregularSPD k_oCurve = new IrregularSPD(Sun_SPD_Data.sun_k_oWavelengths, Sun_SPD_Data.sun_k_oAmplitudes.Select(item=>(float)item).ToArray(), 64);
            IrregularSPD k_gCurve = new IrregularSPD(Sun_SPD_Data.sun_k_gWavelengths, Sun_SPD_Data.sun_k_gAmplitudes, 4);
            IrregularSPD k_waCurve = new IrregularSPD(Sun_SPD_Data.sun_k_waWavelengths, Sun_SPD_Data.sun_k_waAmplitudes.Select(item => (float)item).ToArray(), 13);

            RegularSPD solCurve = new RegularSPD(Sun_SPD_Data.sun_solAmplitudes, 380, 750, 38);  // every 5 nm

            float beta = 0.04608365822050f * turbidity - 0.04586025928522f;
            float tauR, tauA, tauO, tauG, tauWA;

            float m = (1f / (MathLab.Cos(thetaS) + 0.00094f * (float)MathLab.Pow(1.6386f - thetaS, -1.253f)));  // Relative Optical Mass

            int i;
            float lambda;
            // NOTE - lordcrc - SPD stores data internally, no need for Ldata to stick around
            float[] Ldata = new float[91];
            float[] Lambda = new float[91];
            for (i = 0, lambda = 350f; i < 91; ++i, lambda += 5f)
            {
                // Rayleigh Scattering
                Lambda[i] = lambda;
                tauR = MathLab.Exp(-m * 0.008735f * (float)MathLab.Pow(lambda / 1000f, -4.08f));
                // Aerosol (water + dust) attenuation
                // beta - amount of aerosols present
                // alpha - ratio of small to large particle sizes. (0:4,usually 1.3)
                const float alpha = 1.3f;
                tauA = MathLab.Exp(-m * beta * (float)MathLab.Pow(lambda / 1000f, -alpha));  // lambda should be in um
                // Attenuation due to ozone absorption
                // lOzone - amount of ozone in cm(NTP)
                const float lOzone = .35f;
                tauO = MathLab.Exp(-m * k_oCurve.Sample(lambda) * lOzone);
                // Attenuation due to mixed gases absorption
                tauG = MathLab.Exp(-1.41f * k_gCurve.Sample(lambda) * m / (float)MathLab.Pow(1f + 118.93f * k_gCurve.Sample(lambda) * m, 0.45f));
                // Attenuation due to water vapor absorbtion
                // w - precipitable water vapor in centimeters (standard = 2)
                const float w = 2.0f;
                tauWA = MathLab.Exp(-0.2385f * k_waCurve.Sample(lambda) * w * m / (float)MathLab.Pow(1f + 20.07f * k_waCurve.Sample(lambda) * w * m, 0.45f));

                Ldata[i] = (solCurve.Sample(lambda) *
                    tauR * tauA * tauO * tauG * tauWA);
            }
            SampledSpectrum spd = SampledSpectrum.FromSampled(Lambda, Ldata, SpectrumType.Illuminant)*0.01f;
            sunColorSpectra = (spd/(1000000000.0f/(MathLab.M_PI*100f*100000f)));
            suncolor = sunColorSpectra.ToRgb();

            //RegularSPD LSPD = new RegularSPD(Ldata, 350, 800, 91);
            // Note: (1000000000.0f / (M_PI * 100.f * 100.f)) is for compatibility with past scene
            //suncolor =  LSPD.ToRGB() / (1000000000.0f / (MathLab.M_PI * 100f * 100f));
            Console.WriteLine(" suncolor : "+ suncolor);    
        }


        public override ISpectrum Le(ref Vector dir)
        {
            var invs = -sundir;
            ISpectrum res;
            if (cosThetaMax < 1f && Vector.Dot(ref dir, ref invs) > cosThetaMax)
                res = GlobalConfiguration.Instance.SpectralRendering ? (ISpectrum) sunColorSpectra : suncolor;
            else
                res = base.Le(ref dir);
            return res;
        }

        float relSize;
        // XY Vectors for cone sampling
        Vector x, y;
        float cosThetaMax, sin2ThetaMax;
        RgbSpectrum suncolor;
        private SampledSpectrum sunColorSpectra;
    }
}